import * as fs from "fs";
import * as http from "http";
import * as path from "path";
import * as child_process from "child_process";
import { FileTypeResult, fileTypeFromFile } from "file-type";
import FormData from "form-data";
import dayjs from "dayjs";

import config from "./config";

import { getPlanList, getPlanDetail, getProgramMaterial, getDeviceInfoList } from "./api/oldApi";
import { uploadFile as newUploadFile } from "./api/newApi";

/** 下载排期数据 */
export async function downPlanByName(
	planName: string,
	planInfo: any = {},
	targetDir = config.MATERIALS_DIR
): Promise<Record<string, any>> {
	try {
		if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir);

		const planList = await getPlanList(planName).then((res) => res.data.data);
		if (planList.length === 0) {
			console.log(`【${planName}】排期数据没有查询到数据`);
		}

		for (let i = 0; i < planList.length; i++) {
			const plan = planList[i];
			console.log("开始下载排期: ", plan.name);
			if (!planInfo[plan.name]) {
				planInfo[plan.name] = {
					plan,
					programs: {}
				};
			}

			const planAbsolute = path.resolve(targetDir, plan.name);

			if (!fs.existsSync(planAbsolute)) fs.mkdirSync(planAbsolute);

			const programs = await getPlanDetail(plan.id).then((res) => res.data.programs || []);
			if (programs.length === 0) {
				console.log(`【${plan.name}】排期数据没有节目单数据`);
			}
			for (let j = 0; j < programs.length; j++) {
				const program = programs[j];
				console.log("开始下载节目单: ", program.name);
				if (!planInfo[plan.name].programs[program.name]) planInfo[plan.name].programs[program.name] = [];
				const scheduleAbsolute = path.resolve(planAbsolute, program.name);

				if (!fs.existsSync(scheduleAbsolute)) fs.mkdirSync(scheduleAbsolute);

				const materials = await getProgramMaterial(program.id).then((res) => res.data?.data || []);
				if (materials.length === 0) {
					console.log(`【${program.name}】节目单数据没有素材数据`);
				}

				for (let k = 0; k < materials.length; k++) {
					const material = materials[k];
					let materialsAbsolute = path.resolve(scheduleAbsolute, material.fileName);
					const downUrl = config.OLD.DOWNLOAD_URL + material.fileSource;

					if (fs.existsSync(materialsAbsolute)) {
						console.log();
						console.log("文件已经存在: ", materialsAbsolute);
						console.log();
					} else {
						// 这里检查本地其它支行是否已经有下载对应的素材文件, 有则不下载, 则替换 materialsAbsolute 变量
						const someMaterialsAbsolute = findSameMaterial({
							targetDir,
							planName: plan.name,
							programName: program.name,
							materialName: material.fileName
						});
						if (someMaterialsAbsolute) {
							console.log(`从本地找到了相同【排期/节目单/素材名】的文件: 【${someMaterialsAbsolute}】`);
							await fs.promises.writeFile(`${materialsAbsolute}.link`, someMaterialsAbsolute);
							// fs.linkSync(someMaterialsAbsolute, materialsAbsolute);
							materialsAbsolute = someMaterialsAbsolute;
						} else {
							console.log("开始下载素材: ", material.fileName);
							await downFile(downUrl, materialsAbsolute);
						}
					}

					const { duration, type, $fileType } = await getMaterialDurationAndType(materialsAbsolute);

					planInfo[plan.name].programs[program.name].push({
						duration,
						type,
						// 上传后填充
						file: "",
						name: material.fileName,
						resolution: material.fileResolution,
						size: material.fileSize, // TODO 这个文件大小不准确
						officeCode: program.officeId,
						officeName: program.officeName,
						// officeCode: "44000000",
						// officeName: "广东省分行",
						status: "0",
						isShim: "2",
						isUnAudit: "1",

						// 后面需要使用到
						$fileDownload: downUrl,
						$fileType: $fileType,
						$fileAbsolute: materialsAbsolute
					});
				}
			}
		}
	} catch (err) {
		console.log("err: ", err);
	} finally {
		return Promise.resolve(planInfo);
	}
}

/** 下载文件到本地 */
export function downFile(url: string, filePath: string) {
	return new Promise<string>((resolve, reject) => {
		if (fs.existsSync(filePath)) {
			console.log("该文件已经存在不下载: ", filePath);
			return;
		}
		console.log("下载: ", url);
		console.log("到: ", filePath);

		if (config.IS_TEST) {
			fs.writeFileSync(filePath, "");
			resolve(filePath);
			return;
		}

		// 发送请求
		const request = http.get(url, (response) => {
			const statusCode = response.statusCode;
			if (statusCode !== 200) {
				console.log("statusMessage: ", response.statusMessage);
				return;
			}
			const contentType = response.headers["content-cype"];
			const contentLength = parseInt(response.headers["content-length"] || "-1");
			let downLen = 0;
			const ws = fs.createWriteStream(filePath);
			response.pipe(ws);
			let now = Date.now();
			response.on("data", (chunk) => {
				if (Date.now() - now > 1500) {
					now = Date.now();
					downLen += chunk.length;
					const precent = (downLen / contentLength) * 100;
					console.log(`下载进度: ${precent.toFixed(2)}%`);
				}
			});
			response.on("end", () => {
				console.log(`${filePath} 下载完成\n\n`);
				ws.close();
				resolve(filePath);
			});
			request.on("error", (err) => {
				console.log(`下载文件出错: `, err);
				ws.close();
				reject(err);
			});
		});
	});
}

/** 获取视频的时长, 单位是秒 */
export const getVideoDuration = (filePath: string) => {
	return new Promise<Error | string>((resolve, reject) => {
		child_process.exec(
			`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${filePath}"`,
			(error, stdout, stderr) => {
				if (error) {
					reject(error);
					return;
				}
				if (stderr) {
					reject(error);
					return;
				}
				const duration = parseFloat(stdout).toFixed(2);
				resolve(duration);
			}
		);
	});
};

/** 查找相同的素材文件 */
export const findSameMaterial = (opt: {
	/** 目标目录 */
	targetDir: string;
	/** 排期名 */
	planName: string;
	/** 节目单名 */
	programName: string;
	/** 素材名 */
	materialName: string;
}) => {
	const { targetDir, planName, programName, materialName } = opt;
	const basename = path.basename(targetDir);
	const parentDir = path.dirname(targetDir);
	const dirs = fs.readdirSync(parentDir);
	for (let i = 0; i < dirs.length; i++) {
		const dir = dirs[i];
		if (dir !== basename) {
			// 从其它的目录里面寻找有没有对应排期, 节目单, 素材名的文件
			const otherDirMaterialFilePath = path.resolve(parentDir, dir, planName, programName, materialName);
			const exists = fs.existsSync(otherDirMaterialFilePath);
			if (exists) {
				return otherDirMaterialFilePath;
			}
		}
	}
	return "";
};

/** 秒转换为时分秒 */
export const getTime = (time: number | string) => {
	// @ts-ignore
	const h = parseInt((time / 60 / 60) % 24);
	const hh = h < 10 ? `0${h}` : h;

	// @ts-ignore
	const m = parseInt((time / 60) % 60);
	const mm = m < 10 ? `0${m}` : m;

	// @ts-ignore
	const s = parseInt(time % 60);
	const ss = s < 10 ? `0${s}` : s;
	return [hh, mm, ss].join(":");
};

/** 获取素材的的 duration 和 type */
export const getMaterialDurationAndType = (filePath: string) => {
	return new Promise<{
		/** 时长 */
		duration: string;
		/** 素材类型 */
		type: string;
		/** 文件 mini type */
		$fileType: FileTypeResult | null;
	}>(async (resolve) => {
		/** 默认播放时长 */
		const defalutDuration = "00:00:20";
		let duration = defalutDuration;
		let type = "";
		let $fileType: FileTypeResult | null = null;

		try {
			/**
			 * 素材类型
			 * - 1 图片
			 * - 2 文档
			 * - 3 音频
			 * - 4 视频
			 */
			const typeMap: any = {
				image: "1",
				audio: "3",
				video: "4"
			};

			const exists = fs.existsSync(filePath);
			if (exists) $fileType = (await fileTypeFromFile(filePath)) || null;

			if ($fileType) {
				const [_t] = $fileType.mime.split("/");
				// 跟 m180\common\materialManage\column.ts 的素材文件上传的 successFile 保持一致的判断不然app那边会乱
				type = typeMap[_t];

				if ($fileType.mime === "application/pdf") {
					type = "2";
				}
			}

			if (type === "4") {
				const ret = await getVideoDuration(filePath);
				if (ret instanceof Error) {
					console.log("时长获取失败: ", filePath);
				} else if (typeof ret === "string") {
					duration = getTime(ret);
				}
			}
		} catch (err) {
			console.log("解析文件type和duration发生了错误: ", err);
		} finally {
			resolve({
				duration,
				type,
				$fileType
			});
		}
	});
};

/** 上传文件 */
export const uploadFile = (filePath: string) => {
	return new Promise<string>((resolve, reject) => {
		const exists = fs.existsSync(filePath);
		if (!exists) reject(new Error("文件不存在"));

		const formData = new FormData();
		formData.append("bizCode", config.BIZ_CODE);
		formData.append("file", fs.createReadStream(filePath));
		newUploadFile(formData)
			.then((res) => {
				const fileUrl = config.NEW.MINIO_FILE_DOWNURL + res.data?.data;
				resolve(fileUrl);
			})
			.catch((err) => {
				reject(err);
			});
	});
};

/** 计算素材的播放时间 */
export const calcMaterialData = (list: any[], startDate: string, startTime: string) => {
	startDate = startDate.split(" ")[0];
	for (let i = 0; i < list.length; i++) {
		const item = list[i];
		item.sort = i + 1;
		// 第一条则使用父单的开始时间
		if (i === 0) {
			item.startTime = startTime;
		} else {
			// 除了第一条都是使用上一条的结束时间作为这一条的开始时间
			const preItem = list[i - 1];
			// 这里别忘了加1秒
			// item.startTime = dayjs(`${startDate} ${preItem.endTime}`).add(1, 'second').format('HH:mm:ss');
			item.startTime = dayjs(`${startDate} ${preItem.endTime}`).format("HH:mm:ss");
		}

		const _startTime = dayjs(`${startDate} ${item.startTime}`);
		const [h, m, s] = item.duration.split(":").map(Number);
		// TODO 这里存在超过一天的时长显示有问题
		const _endTime = _startTime.add(h, "hour").add(m, "minute").add(s, "second");
		item.startTime = _startTime.format("HH:mm:ss");
		item.endTime = _endTime.format("HH:mm:ss");
	}

	return list;
};
